#include <iostream>
#include <vector>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>

// pid_t fork(void);
// 在Linux中，fork函数是非常重要的函数，它从已存在进程中创建一个新进程。新进程为子进程，而原进程为父进程。
// 在子进程中返回0，父进程中返回子进程的PID，子进程创建失败返回-1。

// fork函数为什么要给子进程返回0，而给父进程返回子进程的PID???
// 1.一个父进程可以创建多个子进程，而一个子进程只能有一个父进程
// 2.因此，对于子进程来说，父进程是不需要被标识的；而对于父进程来说，子进程是需要被标识的，
//      因为父进程创建子进程的目的是让其执行任务的，父进程只有知道了子进程的PID才能很好的对该子进程指派任务。
void test_fork() {

    pid_t pid=fork();

    // 父进程比子进程先挂掉, 所以刚开始child的ppid为father的pid
    // 之后父进程挂掉子进程变成孤儿进程, 被init进程领养, 所以ppid为1

    /*
        [yx@VM-12-12-centos process_control]$ ./myproc 
        i am father, pid: 9251, ppid: 2551
        i am child, pid: 9252, ppid: 9251
        i am father, pid: 9251, ppid: 2551
        i am child, pid: 9252, ppid: 9251
        i am father, pid: 9251, ppid: 2551
        i am child, pid: 9252, ppid: 9251
        [yx@VM-12-12-centos process_control]$ i am child, pid: 9252, ppid: 1
        i am child, pid: 9252, ppid: 1
        i am child, pid: 9252, ppid: 1

        为什么会出现这样的打印呢??? 
        base -> father -> child 
                father挂掉
        base
        init -> child

        因为father先挂掉所以,base不需要等待了也即等待成功直接输出: [yx@VM-12-12-centos process_control]$
        而child被init领养,child没有退出依然向屏幕输出
    */

    if(pid<0) {
        perror("fork");
        exit(1);
    } else if(0 == pid) {
        int n=6;
        while(n!=0) {
            printf("i am child, pid: %d, ppid: %d\n",getpid(),getppid());
            n -= 1;
            sleep(1);
        }
    } else {
        int n=3;
        while(n!=0) {
            printf("i am father, pid: %d, ppid: %d\n",getpid(),getppid());
            n -= 1;
            sleep(1);
        }
    }

    return;

    printf("fork before %d\n",getpid());

    int x=0;

    // 调用fork OS 做了什么
    // 1.创建子进程的进程控制块PCB+进程地址空间+页表等
    // 2.根据父进程的PCB初始化子进程的PCB
    // 3.将子进程(task_struct)添加到系统进程列表当中
    // 4.fork返回，开始调度器调度

    pid_t id=fork();
   
    // 为什么fork函数有两个返回值???
    // 在fork函数内部执行return语句之前，子进程就已经创建完毕了，
    // 那么之后的return语句不仅父进程需要执行，子进程也同样需要执行(子进程继承了父进程的上下文)
    // 这就是fork函数有两个返回值的原因

    printf("fork after %d\n",getpid());

    if(0==id) {
        // child

        x=3;
        printf("child# &x: %p x: %d\n",&x,x);

        exit(0);
    }

    // child# &x: 0x7fffdfaa2268 x: 3
    // parent# &x: 0x7fffdfaa2268 x: 0
    // 为什么地址一样,但x的值却不一样???
    // 因为这里的地址并不是真正的物理地址,而是虚拟地址
    // 父子进程在读取访问x的时候,通过页表将虚拟地址转换成物理地址
    // 物理地址不一样,也即x的值不一样

    // parent

    sleep(5);
    printf("parent# &x: %p x: %d\n",&x,x);
    
    // 1.当子进程刚刚被创建时，子进程和父进程的数据和代码是共享的，
    // 即父子进程的代码和数据通过页表映射到物理内存的同一块空间。
    // 只有当父进程或子进程需要修改数据时，才将父进程的数据在内存当中拷贝一份，然后再进行修改
    // 2.为什么数据要进行写时拷贝???
    // 进程具有独立性。多进程运行，需要独享各种资源，多进程运行期间互不干扰，不能让子进程的修改影响到父进程。
    // 3.为什么不在创建子进程的时候就进行数据的拷贝???
    // 子进程不一定会使用父进程的所有数据，并且在子进程不对数据进行写入的情况下，没有必要对数据进行拷贝，
    // 我们应该按需分配，在需要修改数据的时候再分配（延时分配），这样可以高效的使用内存空间
}   

// 进程退出只有三种情况：1.代码运行完毕，结果正确 2.代码运行完毕，结果不正确 3.代码异常终止（进程崩溃）
void test_exit() {

    // 理解进程退出码: echo $? 查看
    // 为什么以0表示代码执行成功，以非0表示代码执行错误???
    // 因为代码执行成功只有一种情况，成功了就是成功了，而代码执行错误却有多种原因，
    // 例如内存空间不足、非法访问以及栈溢出等等，我们就可以用这些非0的数字分别表示代码执行错误的原因。
    // for(int i=0;i<150;++i) {
    //     printf("%d : %s\n",i,strerror(i));
    // }

    // 2.void exit(int status);
    // std::cout<<"exit";
    // exit(0); // 输出exit
    
    // 3.void _exit(int status);
    std::cout<<"_exit";
    _exit(0); // 没有输出

    // 1.只有在main函数当中的return才能起到退出进程的作用，子函数当中return不能退出进程，
    // 而exit函数和_exit函数在代码中的任何地方使用都可以起到退出进程的作用。
    // 2.使用exit函数退出进程前，exit函数会执行用户定义的清理函数、冲刷缓冲，关闭流等操作，
    // 然后再终止进程，而_exit函数会直接终止进程，不会做任何收尾工作。
}

// 为什么需要进程等待
// 1.子进程退出，父进程如果不读取子进程的退出信息，子进程就会变成僵尸进程，进而造成内存泄漏
// 2.进程一旦变成僵尸进程，那么就算是kill -9命令也无法将其杀死，因为谁也无法杀死一个已经死去的进程
// 3.父进程需要通过进程等待的方式，回收子进程资源，获取子进程的退出信息派发给子进程的任务完成的如何
void test_wait() {

    // // 创建子进程,让子进程直接退出变成僵尸进程验证kill -9 是否能杀死掉僵尸进程
    // // 无法杀死掉僵尸进程
    // pid_t id=fork();
    // if(0==id) {
    //     std::cout<<"child pid: "<<getpid()<<std::endl;
    //     exit(0);
    // }
    // while(true) {

    // }

    //pid_t wait(int *status);
    //status: 输出型参数,0-6位:表示收到的信号 第7位:core_dump表示核心存储是否打开 8-15:表示退出码
    //return pid_t: 等待成功返回被等待进程的pid，等待失败返回-1
    //功能: 等待任意子进程

    pid_t id=fork();

    if(0==id) {

        for(int i=0;i<10;++i) {
            printf("child# pid: %d ppid: %d \n",getpid(),getppid());
            sleep(1);
        }
        exit(0);
    } 

    int status=0;
    pid_t res=wait(&status);

    if(res!=-1) {
        /*
            exitCode = (status >> 8) & 0xFF; //退出码
            exitSignal = status & 0x7F;      //退出信号
        */
        printf("id: %d res: %d exitcode: %d signo: %d core: %d \n",id,res, \
                    (status>>8)&0xff,status&0x7f,status&0x80);

        // WIFEXITED(status)：用于查看进程是否是正常退出，本质是检查是否收到信号
        // WEXITSTATUS(status)：用于获取进程的退出码
        if( WIFEXITED(status) ) {
            // 需要注意的是，当一个进程非正常退出时，说明该进程是被信号所杀，那么该进程的退出码也就没有意义了
            printf("exit code:%d\n", WEXITSTATUS(status));

        }
    }
}

void test_waitpid() {

    printf("WNOHANG: %d\n",WNOHANG); // WNOHANG: 1

    return;

    // pid_t waitpid(pid_t pid, int *status, int options);
    /*
        return pid_t:
            1、等待成功返回被等待进程的pid。
            2、如果设置了选项WNOHANG，而调用中waitpid发现没有已退出的子进程可收集，则返回0
            3、如果调用中出错，则返回-1，这时errno会被设置成相应的值以指示错误所在
        pid:
            待等待子进程的pid，若设置为-1，则等待任意子进程
        options:
            当设置为WNOHANG时，若等待的子进程没有结束，则waitpid函数直接返回0，不予以等待。
            若正常结束，则返回该子进程的pid。传入0表示阻塞式等待
        功能:等待指定子进程或任意子进程
    */

//    std::vector<pid_t> vpid;
//    for(int i=0;i<10;++i) {

//         pid_t id=fork();
//         if(id==0) {

//             std::cout<<"child# "<<getpid()<<std::endl;
//             sleep(3);
            
//             exit(6);
//         }
//         vpid.push_back(id);
//    }

//    for(int i=0;i<10;++i) {

//         int status=0;
//         int res=waitpid(vpid[i],&status,0); // 阻塞式等待
//         if(res>0) {
//             if(WIFEXITED(status)) {
//                 printf("res: %d exitcode: %d\n",res,WEXITSTATUS(status));
//             }
//         }
//    }

    pid_t id=fork();

    if(0==id) {

        std::cout<<"child# "<<getpid()<<std::endl;
        sleep(1);

        exit(0);
    }

    while(1) {
        // 非阻塞式等待
        pid_t ret=waitpid(id,nullptr,WNOHANG); // 没有子进程则返回-1
        if(ret>0) {
            printf("wait success ret: %d\n",ret);
            //break;
        } else if(ret==0) {
            printf("WNOHANG ret: %d\n",ret);
            usleep(1000);
        } else {
            printf("wait fail ret: %d\n",ret);
            break;
        }
    }
}

/*
       extern char **environ;
       int execl(const char *path, const char *arg, ...);
       int execlp(const char *file, const char *arg, ...);
       int execle(const char *path, const char *arg, ..., char * const envp[]);
       int execv(const char *path, char *const argv[]);
       int execvp(const char *file, char *const argv[]);
       int execvpe(const char *file, char *const argv[],char *const envp[]);
*/

// 进程替换
void test_exec() {

    // 当进行进程程序替换时，有没有创建新的进程？
    //  进程程序替换之后，该进程对应的PCB、进程地址空间以及页表等数据结构都没有发生改变，
    //  只是进程在物理内存当中的数据和代码发生了改变，所以并没有创建新的进程，
    //  而且进程程序替换前后该进程的pid并没有改变

    pid_t id=fork();

    if(id==0) {
        //execl("/home/yx/LinuxCode/process_control/myexec","myexec","1","2","3",nullptr);
        //execlp("ls", "ls", "-a", "-i", "-l", NULL);

        char * const envp[]={
            "abc",
            "123",
            "xyz",
            nullptr
        };

        const char* myenvp[] = { "MYVAL=2023", NULL };
        //execle("./myexec","myexec","a","b","c",nullptr,myenvp); // e:表示可以指定环境变量

        char * const myargv[]={
            "./myexec",
            "-x",
            "-y",
            nullptr
        };

        char * const myls[]={"ls", "-a", "-i", "-l", NULL};

        //execv("myexec",myargv);
        //execvp("ls",myls); // p: 表示能自动搜索环境变量PATH，进行程序查找

        /*
            事实上，只有execve才是真正的系统调用，其它五个函数最终都是调用的execve，所以execve在man手册的第2节，而其它五个函数在man手册的第3节，
            也就是说其他五个函数实际上是对系统调用execve进行了封装，以满足不同用户的不同调用场景的
        */
    }
    // 子进程进行进程程序替换后，会影响父进程的代码和数据吗？
    //  子进程刚被创建时，与父进程共享代码和数据，但当子进程需要进行进程程序替换时，
    //  也就意味着子进程需要对其数据和代码进行写入操作，这时便需要将父子进程共享的代码和数据进行写时拷贝，
    //  此后父子进程的代码和数据也就分离了，因此子进程进行程序替换后不会影响父进程的代码和数据。
}

void test_env() {

    // char *getenv(const char *name);
    // 获取对应名称的环境变量的值
    printf("[path] : [%s]\n",getenv("PATH"));

    // int setenv(const char *name, const char *value, int overwrite);
    // 修改对应环境变量的值, overwrite如果为1则表示覆盖, 否则为0表示存在不覆盖
    setenv("PATH","hello",1);

    printf("[path] : [%s]\n",getenv("PATH"));

    // int unsetenv(const char *name);
    // 删除对应的环境变量
    int ret = unsetenv("PATH");
    printf("unsetenv ret: %d\n",ret);

    printf("[path] : [%s]\n",getenv("PATH"));
}

int main(int argc,char *argv[],char *env[]) {

    // printf("myproc# pid: %d\n",getpid());
    // for(int i=0;env[i]!=nullptr;++i) {
    //     std::cout<<env[i]<<std::endl;
    // }

    //test_fork();
    //test_exit();
    //test_wait();
    //test_waitpid();
    //test_exec();
    test_env();

    // 1.main函数return
    return 0;
}

/*
0 : Success
1 : Operation not permitted
2 : No such file or directory
3 : No such process
4 : Interrupted system call
5 : Input/output error
6 : No such device or address
7 : Argument list too long
8 : Exec format error
9 : Bad file descriptor
10 : No child processes
11 : Resource temporarily unavailable
12 : Cannot allocate memory
13 : Permission denied
14 : Bad address
15 : Block device required
16 : Device or resource busy
17 : File exists
18 : Invalid cross-device link
19 : No such device
20 : Not a directory
21 : Is a directory
22 : Invalid argument
23 : Too many open files in system
24 : Too many open files
25 : Inappropriate ioctl for device
26 : Text file busy
27 : File too large
28 : No space left on device
29 : Illegal seek
30 : Read-only file system
31 : Too many links
32 : Broken pipe
33 : Numerical argument out of domain
34 : Numerical result out of range
35 : Resource deadlock avoided
36 : File name too long
37 : No locks available
38 : Function not implemented
39 : Directory not empty
40 : Too many levels of symbolic links
41 : Unknown error 41
42 : No message of desired type
43 : Identifier removed
44 : Channel number out of range
45 : Level 2 not synchronized
46 : Level 3 halted
47 : Level 3 reset
48 : Link number out of range
49 : Protocol driver not attached
50 : No CSI structure available
51 : Level 2 halted
52 : Invalid exchange
53 : Invalid request descriptor
54 : Exchange full
55 : No anode
56 : Invalid request code
57 : Invalid slot
58 : Unknown error 58
59 : Bad font file format
60 : Device not a stream
61 : No data available
62 : Timer expired
63 : Out of streams resources
64 : Machine is not on the network
65 : Package not installed
66 : Object is remote
67 : Link has been severed
68 : Advertise error
69 : Srmount error
70 : Communication error on send
71 : Protocol error
72 : Multihop attempted
73 : RFS specific error
74 : Bad message
75 : Value too large for defined data type
76 : Name not unique on network
77 : File descriptor in bad state
78 : Remote address changed
79 : Can not access a needed shared library
80 : Accessing a corrupted shared library
81 : .lib section in a.out corrupted
82 : Attempting to link in too many shared libraries
83 : Cannot exec a shared library directly
84 : Invalid or incomplete multibyte or wide character
85 : Interrupted system call should be restarted
86 : Streams pipe error
87 : Too many users
88 : Socket operation on non-socket
89 : Destination address required
90 : Message too long
91 : Protocol wrong type for socket
92 : Protocol not available
93 : Protocol not supported
94 : Socket type not supported
95 : Operation not supported
96 : Protocol family not supported
97 : Address family not supported by protocol
98 : Address already in use
99 : Cannot assign requested address
100 : Network is down
101 : Network is unreachable
102 : Network dropped connection on reset
103 : Software caused connection abort
104 : Connection reset by peer
105 : No buffer space available
106 : Transport endpoint is already connected
107 : Transport endpoint is not connected
108 : Cannot send after transport endpoint shutdown
109 : Too many references: cannot splice
110 : Connection timed out
111 : Connection refused
112 : Host is down
113 : No route to host
114 : Operation already in progress
115 : Operation now in progress
116 : Stale file handle
117 : Structure needs cleaning
118 : Not a XENIX named type file
119 : No XENIX semaphores available
120 : Is a named type file
121 : Remote I/O error
122 : Disk quota exceeded
123 : No medium found
124 : Wrong medium type
125 : Operation canceled
126 : Required key not available
127 : Key has expired
128 : Key has been revoked
129 : Key was rejected by service
130 : Owner died
131 : State not recoverable
132 : Operation not possible due to RF-kill
133 : Memory page has hardware error
134 : Unknown error 134
135 : Unknown error 135
136 : Unknown error 136
137 : Unknown error 137
138 : Unknown error 138
139 : Unknown error 139
*/